using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.EventArgs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace RaceHFTools.BluetoothLE.Bean.Module
{
    #region 命令表
    public enum TWEAK_GROUP
    {
        BASE,
        DEVICE,
        USER,
        PRO,
        RECORD,
        GNSS,
        GSENSOR,
        SDCARD,
    };

    public enum TWEAK_BASE
    {
        GET_ALL,

        GET_VERSION,
    };

    public enum TWEAK_DEVICE
    {
        GET_ALL,

        GET_NAME,
        GET_MODEL,
        GET_ID,
        GET_HW_VERSION,
        GET_HW_BATCH,
        GET_FW_VERSION,
        GET_FW_NUMBER,
        GET_BL_VERSION,
        GET_LAST_POWER_TIME,
        SET_POWER,
        GET_TIMEZONE,
        SET_TIMEZONE,
    };

    public enum TWEAK_USER
    {
        GET_ALL,

        GET_ID,
        SET_ID,
        GET_NICK,
        SET_NICK,
    };

    public enum TWEAK_PRO
    {
        GET_ALL,

        GET_BATTERY,
        SET_BATTERY,
        GET_GSENSOR,
        SET_GSENSOR,
        GET_GNSS,
        SET_GNSS,
        GET_AGPS,
        SET_AGPS,
        GET_FLASH,
        SET_FLASH,
        GET_SDCARD,
        SET_SDCARD,
        GET_FSYNC,
        SET_FSYNC,
    };

    public enum TWEAK_RECORD
    {
        GET_ALL,

        GET_TRIG_TYPE,
        SET_TRIG_TYPE,
        GET_FILE_TYPE,
        SET_FILE_TYPE,
    };

    public enum TWEAK_GNSS
    {
        GET_ALL,

        GET_RATE,
        SET_RATE,
        GET_GNSS,
        SET_GNSS,
        GET_NAVX5,
        SET_NAVX5,
    };

    public enum TWEAK_GSENSOR
    {
        GET_ALL,

        GET_RATE,
        SET_RATE,
    };

    public enum TWEAK_SDCARD
    {
        GET_ALL,

        GET_SPACE,
        SET_FORMAT,
        GET_FAIL_REASON = 0xf0,
    };
    #endregion

    #region 命令参数
    public enum TweakDevicePowerMode
    {
        PowerOff,
        Restart,
    };

    public enum TweakProIndex
    {
        ALL,
        BATTERY,
        GSENSOR,
        GNSS,
        AGPS,
        FLASH,
        SDCARD,
        FSYNC
    };

    public struct TweakProType
    {
        public TweakProIndex index;
        public bool enable;
        public uint validtime;
    };

    public enum TweakRecordTrigType
    {
        ByGnss,
        BySpeed,
    };

    public struct TweakRecordFileType
    {
        public bool nma;
        public bool acc;
        public bool rhf;
        public bool vbo;
    };

    public enum TweakSdcardFailReason
    {
        NONE = 0x00,
        PLUGIN,
        MOUNT,
        COMPAT,

        INTERNAL,
        UNKOWN = 0xff,
    };
    #endregion

    #region 回调声明
    public delegate void TweakBaseGetVersionEventHandler(uint version);
    public delegate void TweakDeviceGetNameEventHandler(string name);
    public delegate void TweakDeviceGetModelEventHandler(string model);
    public delegate void TweakDeviceGetIdEventHandler(byte[] id);
    public delegate void TweakDeviceGetHwVersionEventHandler(string version);
    public delegate void TweakDeviceGetHwBatchEventHandler(string batch);
    public delegate void TweakDeviceGetFwVersionEventHandler(string version);
    public delegate void TweakDeviceGetFwNumberEventHandler(uint number);
    public delegate void TweakDeviceGetBlVersionEventHandler(string version);
    public delegate void TweakDeviceGetLastPowerTimeEventHandler(uint time);
    public delegate void TweakDeviceSetPowerEventHandler(bool state);
    public delegate void TweakDeviceGetTimezoneEventHandler(int timezone);
    public delegate void TweakDeviceSetTimezoneEventHandler(bool state);
    public delegate void TweakUserGetIdEventHandler(uint id);
    public delegate void TweakUserSetIdEventHandler(bool state);
    public delegate void TweakUserGetNickEventHandler(string nick);
    public delegate void TweakUserSetNickEventHandler(bool state);
    public delegate void TweakProGetProEventHandler(TweakProType code);
    public delegate void TweakProSetProEventHandler(bool state);
    public delegate void TweakRecordGetTrigEventHandler(TweakRecordTrigType type);
    public delegate void TweakRecordSetTrigEventHandler(bool state);
    public delegate void TweakRecordGetFileEventHandler(TweakRecordFileType type);
    public delegate void TweakRecordSetFileEventHandler(bool state);
    public delegate void TweakGnssGetRateEventHandler(uint rate);
    public delegate void TweakGnssSetRateEventHandler(bool state);
    public delegate void TweakGnssGetGnssEventHandler(bool gps, bool glonass, bool galileo, bool beidou);
    public delegate void TweakGnssSetGnssEventHandler(bool state);
    public delegate void TweakGnssGetNavx5EventHandler(uint min_sv, uint max_sv, uint min_noise);
    public delegate void TweakGnssSetNavx5EventHandler(bool state);
    public delegate void TweakGsensorGetRateEventHandler(uint rate);
    public delegate void TweakGsensorSetRateEventHandler(bool state);
    public delegate void TweakSdcardGetSpaceEventHandler(uint total, uint free);
    public delegate void TweakSdcardGetFailEventHandler(TweakSdcardFailReason reason);
    public delegate void TweakSdcardSetFormatEventHandler(bool state);
    #endregion

    public class Tweak
    {
        public readonly Guid TWEAK_CFG_UUID = new Guid("0000aaa3-0000-1000-8000-00805f9b34fb");

        #region 回调事件
        public TweakBaseGetVersionEventHandler BaseGetVersionEvent;
        public TweakDeviceGetNameEventHandler DeviceGetNameEvent;
        public TweakDeviceGetModelEventHandler DeviceGetModelEvent;
        public TweakDeviceGetIdEventHandler DeviceGetIdEvent;
        public TweakDeviceGetHwVersionEventHandler DeviceGetHwVersionEvent;
        public TweakDeviceGetHwBatchEventHandler DeviceGetHwBatchEvent;
        public TweakDeviceGetFwVersionEventHandler DeviceGetFwVersionEvent;
        public TweakDeviceGetFwNumberEventHandler DeviceGetFwNumberEvent;
        public TweakDeviceGetBlVersionEventHandler DeviceGetBlVersionEvent;
        public TweakDeviceGetLastPowerTimeEventHandler DeviceGetLastPowerTimeEvent;
        public TweakDeviceSetPowerEventHandler DeviceSetPowerEvent;
        public TweakDeviceGetTimezoneEventHandler DeviceGetTimezoneEvent;
        public TweakDeviceSetTimezoneEventHandler DeviceSetTimezoneEvent;
        public TweakUserGetIdEventHandler UserGetIdEvent;
        public TweakUserSetIdEventHandler UserSetIdEvent;
        public TweakUserGetNickEventHandler UserGetNickEvent;
        public TweakUserSetNickEventHandler UserSetNickEvent;
        public TweakProGetProEventHandler ProGetEvent;
        public TweakProSetProEventHandler ProSetEvent;
        public TweakRecordGetTrigEventHandler RecordGetTrigEvent;
        public TweakRecordSetTrigEventHandler RecordSetTrigEvent;
        public TweakRecordGetFileEventHandler RecordGetFileEvent;
        public TweakRecordSetFileEventHandler RecordSetFileEvent;
        public TweakGnssGetRateEventHandler GnssGetRateEvent;
        public TweakGnssSetRateEventHandler GnssSetRateEvent;
        public TweakGnssGetGnssEventHandler GnssGetGnssEvent;
        public TweakGnssSetGnssEventHandler GnssSetGnssEvent;
        public TweakGnssGetNavx5EventHandler GnssGetNavx5Event;
        public TweakGnssSetNavx5EventHandler GnssSetNavx5Event;
        public TweakGsensorGetRateEventHandler GsensorGetRateEvent;
        public TweakGsensorSetRateEventHandler GsensorSetRateEvent;
        public TweakSdcardGetSpaceEventHandler SdcardGetSpaceEvent;
        public TweakSdcardGetFailEventHandler SdcardGetFailEvent;
        public TweakSdcardSetFormatEventHandler SdcardSetFormat;

        #endregion

        readonly Bean Bean;
        readonly ICharacteristic tweak_cfg_char;

        #region 总控
        public Tweak(Bean bean)
        {
            Bean = bean;
            if (Bean == null)
                throw new Exception("No valid device");

            tweak_cfg_char = Bean.Transport.FindCharacteristicById(TWEAK_CFG_UUID);
            if (tweak_cfg_char == null)
                throw new Exception("No valid characteristic");
            tweak_cfg_char.WriteType = CharacteristicWriteType.WithoutResponse;
        }

        public async Task Init()
        {
            await Bean.Transport.EnableNotify(tweak_cfg_char);
            tweak_cfg_char.ValueUpdated += NotifyHandler;
        }

        public async Task Deinit()
        {
            tweak_cfg_char.ValueUpdated -= NotifyHandler;
            await Bean.Transport.DisableNotify(tweak_cfg_char);
        }

        void NotifyHandler(object sender, CharacteristicUpdatedEventArgs e)
        {
            ICharacteristic characteristic = e.Characteristic;
            byte[] val = characteristic.Value;

            if (val.Length == 0)
                return;

            GroupAckHandler(val[0], val.Skip(1).ToArray());
        }
        #endregion

        #region 命令分发
        void GroupAckHandler(byte group, byte[] data)
        {
            switch ((TWEAK_GROUP)group)
            {
                case TWEAK_GROUP.BASE:
                    BaseAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                case TWEAK_GROUP.DEVICE:
                    DeviceAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                case TWEAK_GROUP.USER:
                    UserAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                case TWEAK_GROUP.PRO:
                    ProAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                case TWEAK_GROUP.RECORD:
                    RecordAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                case TWEAK_GROUP.GNSS:
                    GnssAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                case TWEAK_GROUP.GSENSOR:
                    GsensorAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                case TWEAK_GROUP.SDCARD:
                    SdcardAckHandler(data[0], data.Skip(1).ToArray());
                    break;

                default:
                    break;
            }
        }

        void BaseAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;
            if (length < 2)
                return;

            switch ((TWEAK_BASE)cmd)
            {
                case TWEAK_BASE.GET_VERSION:
                    if (data.Length == 4)
                    {
                        uint version = BitConverter.ToUInt32(data, 0);
                        BaseGetVersionEvent?.Invoke(version);
                    }
                    break;

                default:
                    break;
            }
        }

        void DeviceAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;

            switch ((TWEAK_DEVICE)cmd)
            {
                case TWEAK_DEVICE.GET_NAME:
                    if (length > 0)
                    {
                        string name = Encoding.ASCII.GetString(data);
                        DeviceGetNameEvent?.Invoke(name);
                    }
                    break;

                case TWEAK_DEVICE.GET_MODEL:
                    if (length > 0)
                    {
                        string model = Encoding.ASCII.GetString(data);
                        DeviceGetModelEvent?.Invoke(model);
                    }
                    break;

                case TWEAK_DEVICE.GET_ID:
                    if (length == 16)
                    {
                        byte[] id = data;
                        DeviceGetIdEvent?.Invoke(id);
                    }
                    break;

                case TWEAK_DEVICE.GET_HW_VERSION:
                    if (length > 0)
                    {
                        string version = Encoding.ASCII.GetString(data);
                        DeviceGetHwVersionEvent?.Invoke(version);
                    }
                    break;

                case TWEAK_DEVICE.GET_HW_BATCH:
                    if (length > 0)
                    {
                        string batch = Encoding.ASCII.GetString(data);
                        DeviceGetHwBatchEvent?.Invoke(batch);
                    }
                    break;

                case TWEAK_DEVICE.GET_FW_VERSION:
                    if (length > 0)
                    {
                        string version = Encoding.ASCII.GetString(data);
                        DeviceGetFwVersionEvent?.Invoke(version);
                    }
                    break;

                case TWEAK_DEVICE.GET_FW_NUMBER:
                    if (length == 4)
                    {
                        uint num = BitConverter.ToUInt32(data, 0);
                        DeviceGetFwNumberEvent?.Invoke(num);
                    }
                    break;

                case TWEAK_DEVICE.GET_BL_VERSION:
                    if (length > 0)
                    {
                        string version = Encoding.ASCII.GetString(data);
                        DeviceGetBlVersionEvent?.Invoke(version);
                    }
                    break;

                case TWEAK_DEVICE.GET_LAST_POWER_TIME:
                    if (length == 4)
                    {
                        uint time = BitConverter.ToUInt32(data, 0);
                        DeviceGetLastPowerTimeEvent?.Invoke(time);
                    }
                    break;

                case TWEAK_DEVICE.SET_POWER:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        DeviceSetPowerEvent?.Invoke(state);
                    }
                    break;

                case TWEAK_DEVICE.GET_TIMEZONE:
                    if (length == 1)
                    {
                        int timezone = data[0];
                        DeviceGetTimezoneEvent?.Invoke(timezone);
                    }
                    break;

                case TWEAK_DEVICE.SET_TIMEZONE:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        DeviceSetTimezoneEvent?.Invoke(state);
                    }
                    break;

                default:
                    break;
            }
        }

        void UserAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;

            switch ((TWEAK_USER)cmd)
            {
                case TWEAK_USER.GET_ID:
                    if (length == 4)
                    {
                        uint id = BitConverter.ToUInt32(data, 0);
                        UserGetIdEvent?.Invoke(id);
                    }
                    break;

                case TWEAK_USER.SET_ID:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        UserSetIdEvent?.Invoke(state);
                    }
                    break;

                case TWEAK_USER.GET_NICK:
                    if (length > 0)
                    {
                        string nick = Encoding.ASCII.GetString(data);
                        UserGetNickEvent?.Invoke(nick);
                    }
                    break;

                case TWEAK_USER.SET_NICK:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        UserSetNickEvent?.Invoke(state);
                    }
                    break;

                default:
                    break;
            }
        }

        void ProAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;

            switch ((TWEAK_PRO)cmd)
            {
                case TWEAK_PRO.GET_BATTERY:
                case TWEAK_PRO.GET_GSENSOR:
                case TWEAK_PRO.GET_GNSS:
                case TWEAK_PRO.GET_AGPS:
                case TWEAK_PRO.GET_FLASH:
                case TWEAK_PRO.GET_SDCARD:
                case TWEAK_PRO.GET_FSYNC:
                    if (length == 7)
                    {
                        uint pidx = BitConverter.ToUInt16(data, 0);
                        bool pen = BitConverter.ToBoolean(data, 2);
                        uint ptime = BitConverter.ToUInt32(data, 3);
                        ProGetEvent?.Invoke(new TweakProType { index = (TweakProIndex)pidx, enable = pen, validtime = ptime });
                    }
                    break;

                case TWEAK_PRO.SET_BATTERY:
                case TWEAK_PRO.SET_GSENSOR:
                case TWEAK_PRO.SET_GNSS:
                case TWEAK_PRO.SET_AGPS:
                case TWEAK_PRO.SET_FLASH:
                case TWEAK_PRO.SET_SDCARD:
                case TWEAK_PRO.SET_FSYNC:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        ProSetEvent?.Invoke(state);
                    }
                    break;

                default:
                    break;
            }
        }

        void RecordAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;

            switch ((TWEAK_RECORD)cmd)
            {
                case TWEAK_RECORD.GET_TRIG_TYPE:
                    if (length == 1)
                    {
                        TweakRecordTrigType type = (TweakRecordTrigType)data[0];
                        RecordGetTrigEvent?.Invoke(type);
                    }
                    break;

                case TWEAK_RECORD.SET_TRIG_TYPE:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        RecordSetTrigEvent?.Invoke(state);
                    }
                    break;

                case TWEAK_RECORD.GET_FILE_TYPE:
                    if (length == 4)
                    {
                        uint fcode = BitConverter.ToUInt32(data, 0);
                        TweakRecordFileType type;
                        type.nma = (fcode & (1 << 0)) != 0;
                        type.acc = (fcode & (1 << 1)) != 0;
                        type.rhf = (fcode & (1 << 2)) != 0;
                        type.vbo = (fcode & (1 << 3)) != 0;
                        RecordGetFileEvent?.Invoke(type);
                    }
                    break;

                case TWEAK_RECORD.SET_FILE_TYPE:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        RecordSetFileEvent?.Invoke(state);
                    }
                    break;

                default:
                    break;
            }
        }

        void GnssAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;

            switch ((TWEAK_GNSS)cmd)
            {
                case TWEAK_GNSS.GET_RATE:
                    if (length == 1)
                    {
                        uint rate = data[0];
                        GnssGetRateEvent?.Invoke(rate);
                    }
                    break;

                case TWEAK_GNSS.SET_RATE:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        GnssSetRateEvent?.Invoke(state);
                    }
                    break;

                case TWEAK_GNSS.GET_GNSS:
                    if (length == 1)
                    {
                        byte gcode = data[0];
                        bool gps = (gcode & (1 << 0)) != 0;
                        bool glonass = (gcode & (1 << 1)) != 0;
                        bool galileo = (gcode & (1 << 2)) != 0;
                        bool beidou = (gcode & (1 << 3)) != 0;
                        GnssGetGnssEvent?.Invoke(gps, glonass, galileo, beidou);
                    }
                    break;

                case TWEAK_GNSS.SET_GNSS:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        GnssSetGnssEvent?.Invoke(state);
                    }
                    break;

                case TWEAK_GNSS.GET_NAVX5:
                    if (length == 3)
                    {
                        uint min_sv = data[0];
                        uint max_sv = data[1];
                        uint min_noise = data[2];
                        GnssGetNavx5Event?.Invoke(min_sv, max_sv, min_noise);
                    }
                    break;

                case TWEAK_GNSS.SET_NAVX5:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        GnssSetNavx5Event?.Invoke(state);
                    }
                    break;
            }
        }

        void GsensorAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;

            switch ((TWEAK_GSENSOR)cmd)
            {
                case TWEAK_GSENSOR.GET_RATE:
                    if (length == 2)
                    {
                        uint rate = BitConverter.ToUInt16(data, 0);
                        GsensorGetRateEvent?.Invoke(rate);
                    }
                    break;

                case TWEAK_GSENSOR.SET_RATE:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        GsensorSetRateEvent?.Invoke(state);
                    }
                    break;

                default:
                    break;
            }
        }

        void SdcardAckHandler(byte cmd, byte[] data)
        {
            int length = data.Length;

            switch ((TWEAK_SDCARD)cmd)
            {
                case TWEAK_SDCARD.GET_SPACE:
                    if (length == 8)
                    {
                        uint total = BitConverter.ToUInt32(data, 0);
                        uint free = BitConverter.ToUInt32(data, 4);
                        SdcardGetSpaceEvent?.Invoke(total, free);
                    }
                    break;

                case TWEAK_SDCARD.SET_FORMAT:
                    if (length == 1)
                    {
                        bool state = data[0] == 0;
                        SdcardSetFormat?.Invoke(state);
                    }
                    break;

                case TWEAK_SDCARD.GET_FAIL_REASON:
                    if (length == 1)
                    {
                        TweakSdcardFailReason reason = (TweakSdcardFailReason)data[0];
                        SdcardGetFailEvent?.Invoke(reason);
                    }
                    break;

                default:
                    break;
            }
        }
        #endregion

        #region 命令下发
        async Task SendCommand(byte group, byte cmd, byte[] data = null)
        {
            byte[] command = new byte[] { group, cmd };
            if (data != null)
                command = command.Concat(data).ToArray();
            await Bean.Transport.Write(tweak_cfg_char, command);
        }
        #endregion

        #region Base命令
        public async Task GetBaseAll()
        {
            await SendCommand((byte)TWEAK_GROUP.BASE, (byte)TWEAK_BASE.GET_ALL);
        }

        public async Task GetBaseVersion()
        {
            await SendCommand((byte)TWEAK_GROUP.BASE, (byte)TWEAK_BASE.GET_VERSION);
        }
        #endregion

        #region Device命令
        public async Task GetDeviceAll()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_ALL);
        }

        public async Task GetDeviceName()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_NAME);
        }

        public async Task GetDeviceModel()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_MODEL);
        }

        public async Task GetDeviceId()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_ID);
        }

        public async Task GetDeviceHwVersion()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_HW_VERSION);
        }

        public async Task GetDeviceHwBatch()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_HW_BATCH);
        }

        public async Task GetDeviceFwVersion()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_FW_VERSION);
        }

        public async Task GetDeviceFwNumber()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_FW_NUMBER);
        }

        public async Task GetDeviceBlVersion()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_BL_VERSION);
        }

        public async Task GetDeviceLastPowerTime()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_LAST_POWER_TIME);
        }

        public async Task SetDevicePower(TweakDevicePowerMode mode)
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.SET_POWER, new byte[] { (byte)mode });
        }

        public async Task GetDeviceTimezone()
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.GET_TIMEZONE);
        }

        public async Task SetDeviceTimezone(int timezone)
        {
            await SendCommand((byte)TWEAK_GROUP.DEVICE, (byte)TWEAK_DEVICE.SET_TIMEZONE, new byte[] { (byte)timezone });
        }
        #endregion

        #region User命令
        public async Task GetUserAll()
        {
            await SendCommand((byte)TWEAK_GROUP.USER, (byte)TWEAK_USER.GET_ALL);
        }

        public async Task GetUserId()
        {
            await SendCommand((byte)TWEAK_GROUP.USER, (byte)TWEAK_USER.GET_ID);
        }

        public async Task SetUserId(uint id)
        {
            await SendCommand((byte)TWEAK_GROUP.USER, (byte)TWEAK_USER.SET_ID, BitConverter.GetBytes(id));
        }

        public async Task GetUserNick()
        {
            await SendCommand((byte)TWEAK_GROUP.USER, (byte)TWEAK_USER.GET_NICK);
        }

        public async Task SetUserNick(string nick)
        {
            await SendCommand((byte)TWEAK_GROUP.USER, (byte)TWEAK_USER.SET_NICK, nick == null ? null : Encoding.ASCII.GetBytes(nick));
        }
        #endregion

        #region Pro命令
        public async Task GetPro(TweakProIndex index)
        {
            byte cmd = (byte)TWEAK_PRO.GET_ALL;
            switch (index)
            {
                case TweakProIndex.BATTERY: cmd = (byte)TWEAK_PRO.GET_BATTERY; break;
                case TweakProIndex.GSENSOR: cmd = (byte)TWEAK_PRO.GET_GSENSOR; break;
                case TweakProIndex.GNSS: cmd = (byte)TWEAK_PRO.GET_GNSS; break;
                case TweakProIndex.AGPS: cmd = (byte)TWEAK_PRO.GET_AGPS; break;
                case TweakProIndex.FLASH: cmd = (byte)TWEAK_PRO.GET_FLASH; break;
                case TweakProIndex.SDCARD: cmd = (byte)TWEAK_PRO.GET_SDCARD; break;
                case TweakProIndex.FSYNC: cmd = (byte)TWEAK_PRO.GET_FSYNC; break;
                default: break;
            }
            await SendCommand((byte)TWEAK_GROUP.PRO, cmd);
        }

        public async Task SetPro(TweakProType pro, byte[] id, uint utc)
        {
            List<byte> pcode = new List<byte>();
            pcode.AddRange(BitConverter.GetBytes((ushort)pro.index));
            pcode.AddRange(BitConverter.GetBytes(pro.enable));
            pcode.AddRange(BitConverter.GetBytes((uint)19951024));
            byte cmd = (byte)TWEAK_PRO.GET_ALL;
            switch (pro.index)
            {
                case TweakProIndex.BATTERY: cmd = (byte)TWEAK_PRO.SET_BATTERY; break;
                case TweakProIndex.GSENSOR: cmd = (byte)TWEAK_PRO.SET_GSENSOR; break;
                case TweakProIndex.GNSS: cmd = (byte)TWEAK_PRO.SET_GNSS; break;
                case TweakProIndex.AGPS: cmd = (byte)TWEAK_PRO.SET_AGPS; break;
                case TweakProIndex.FLASH: cmd = (byte)TWEAK_PRO.SET_FLASH; break;
                case TweakProIndex.SDCARD: cmd = (byte)TWEAK_PRO.SET_SDCARD; break;
                case TweakProIndex.FSYNC: cmd = (byte)TWEAK_PRO.SET_FSYNC; break;
                default: break;
            }
            await SendCommand((byte)TWEAK_GROUP.PRO, cmd, EncryptProCode(pcode.ToArray(), id, utc));
        }
        #endregion

        #region Record命令
        public async Task GetRecordAll()
        {
            await SendCommand((byte)TWEAK_GROUP.RECORD, (byte)TWEAK_RECORD.GET_ALL);
        }

        public async Task GetRecordTrigType()
        {
            await SendCommand((byte)TWEAK_GROUP.RECORD, (byte)TWEAK_RECORD.GET_TRIG_TYPE);
        }

        public async Task SetRecordTrigType(TweakRecordTrigType type)
        {
            await SendCommand((byte)TWEAK_GROUP.RECORD, (byte)TWEAK_RECORD.SET_TRIG_TYPE, new byte[] { (byte)type });
        }

        public async Task GetRecordFileType()
        {
            await SendCommand((byte)TWEAK_GROUP.RECORD, (byte)TWEAK_RECORD.GET_FILE_TYPE);
        }

        public async Task SetRecordFileType(TweakRecordFileType type)
        {
            uint fcode = 0;
            if (type.nma) fcode |= (1 << 0);
            if (type.acc) fcode |= (1 << 1);
            if (type.rhf) fcode |= (1 << 2);
            if (type.vbo) fcode |= (1 << 3);

            await SendCommand((byte)TWEAK_GROUP.RECORD, (byte)TWEAK_RECORD.SET_FILE_TYPE, BitConverter.GetBytes(fcode));
        }
        #endregion

        #region Gnss命令
        public async Task GetGnssAll()
        {
            await SendCommand((byte)TWEAK_GROUP.GNSS, (byte)TWEAK_GNSS.GET_ALL);
        }

        public async Task GetGnssRate()
        {
            await SendCommand((byte)TWEAK_GROUP.GNSS, (byte)TWEAK_GNSS.GET_RATE);
        }

        public async Task SetGnssRate(uint rate)
        {
            await SendCommand((byte)TWEAK_GROUP.GNSS, (byte)TWEAK_GNSS.SET_RATE, new byte[] { (byte)rate });
        }

        public async Task GetGnssGnss()
        {
            await SendCommand((byte)TWEAK_GROUP.GNSS, (byte)TWEAK_GNSS.GET_GNSS);
        }

        public async Task SetGnssGnss(bool gps, bool glonass, bool galileo, bool beiduo)
        {
            byte gcode = 0;
            if (gps) gcode |= (1 << 0);
            if (glonass) gcode |= (1 << 1);
            if (galileo) gcode |= (1 << 2);
            if (beiduo) gcode |= (1 << 3);

            await SendCommand((byte)TWEAK_GROUP.GNSS, (byte)TWEAK_GNSS.SET_GNSS, new byte[] { gcode });
        }

        public async Task GetGnssNavx5()
        {
            await SendCommand((byte)TWEAK_GROUP.GNSS, (byte)TWEAK_GNSS.GET_NAVX5);
        }

        public async Task SetGnssNavx5(uint min_sv, uint max_sv, uint min_noise)
        {
            await SendCommand((byte)TWEAK_GROUP.GNSS, (byte)TWEAK_GNSS.SET_NAVX5, new byte[] { (byte)min_sv, (byte)max_sv, (byte)min_noise });
        }
        #endregion

        #region Gsensor命令
        public async Task GetGsensorAll()
        {
            await SendCommand((byte)TWEAK_GROUP.GSENSOR, (byte)TWEAK_GSENSOR.GET_ALL);
        }

        public async Task GetGsensorRate()
        {
            await SendCommand((byte)TWEAK_GROUP.GSENSOR, (byte)TWEAK_GSENSOR.GET_RATE);
        }

        public async Task SetGsensorRate(uint rate)
        {
            await SendCommand((byte)TWEAK_GROUP.GSENSOR, (byte)TWEAK_GSENSOR.SET_RATE, BitConverter.GetBytes((ushort)rate));
        }
        #endregion

        #region Sdcard命令
        public async Task GetSdcardAll()
        {
            await SendCommand((byte)TWEAK_GROUP.SDCARD, (byte)TWEAK_SDCARD.GET_ALL);
        }

        public async Task GetSdcardSpace()
        {
            await SendCommand((byte)TWEAK_GROUP.SDCARD, (byte)TWEAK_SDCARD.GET_SPACE);
        }

        public async Task GetSdcardFailReason()
        {
            await SendCommand((byte)TWEAK_GROUP.SDCARD, (byte)TWEAK_SDCARD.GET_FAIL_REASON);
        }

        public async Task SetSdcardFormat()
        {
            await SendCommand((byte)TWEAK_GROUP.SDCARD, (byte)TWEAK_SDCARD.SET_FORMAT);
        }
        #endregion

        #region 工具函数
        ushort GetShortId(byte[] id)
        {
            if (id?.Length != 16)
                return 0;

            byte a = 0, b = 0;
            foreach (byte x in id)
            {
                a += x;
                b += a;
            }

            return (ushort)(((b & 0xff) << 8) | (a & 0xff));
        }

        byte[] EncryptProCode(byte[] pcode, byte[] id, uint utc)
        {
            if (id?.Length != 16)
                return null;

            byte[] iv = new byte[] { 0x59, 0x0e, 0x9a, 0xc7, 0xcc, 0x84, 0x4c, 0xdf, 0xb9, 0xf3, 0xec, 0x89, 0x24, 0x75, 0x3c, 0xdb };

            byte[] key = new byte[16];
            uint key_magic = 0x95102405;
            ushort sid = GetShortId(id);
            uint cid = BitConverter.ToUInt32(id, 12);
            BitConverter.GetBytes(key_magic).CopyTo(key, 0);
            BitConverter.GetBytes(sid).CopyTo(key, 4);
            BitConverter.GetBytes(cid).CopyTo(key, 6);
            BitConverter.GetBytes(utc).CopyTo(key, 10);

            byte[] plain = new byte[16];
            pcode.CopyTo(plain, 0);

            RijndaelManaged rm = new RijndaelManaged
            {
                Key = key,
                IV = iv,
                Mode = CipherMode.CBC,
                Padding = PaddingMode.Zeros
            };

            ICryptoTransform ctf = rm.CreateEncryptor();
            byte[] cipher = ctf.TransformFinalBlock(plain, 0, plain.Length);

            return cipher;
        }
        #endregion
    }
}
